package main

import (
	"context"
	"flag"
	"fmt"
	"gitee.com/whilew/gen"
	"gitee.com/whilew/gen/internal/check"
	"gorm.io/driver/mysql"
	"gorm.io/driver/postgres"
	"gorm.io/driver/sqlite"
	"gorm.io/driver/sqlserver"
	"gorm.io/gorm"
	"log"
	"os"
)

// DBType database type
type DBType string

const (
	//	Gorm Drivers mysql || postgres || sqlite || sqlserver
	DBMySQL     DBType = "mysql"
	DBPostgres  DBType = "postgres"
	DBSQLite    DBType = "sqlite"
	DBSQLServer DBType = "sqlserver"
)

func connectDB(t DBType, dsn string) (*gorm.DB, error) {
	if dsn == "" {
		return nil, fmt.Errorf("dsn cannot be empty")
	}

	switch t {
	case DBMySQL:
		return gorm.Open(mysql.Open(dsn))
	case DBPostgres:
		return gorm.Open(postgres.Open(dsn))
	case DBSQLite:
		return gorm.Open(sqlite.Open(dsn))
	case DBSQLServer:
		return gorm.Open(sqlserver.Open(dsn))
	default:
		return nil, fmt.Errorf("unknow db %q (support mysql || postgres || sqlite || sqlserver for now)", t)
	}
}

func getModels(g *gen.Generator, db *gorm.DB, tables []string) (models []interface{}, err error) {
	if len(tables) == 0 {
		//Execute tasks for all tables in the database
		tables, err = db.Migrator().GetTables()
		if err != nil {
			return nil, fmt.Errorf("GORM migrator get all tables fail: %w", err)
		}
	}

	//Execute some data table tasks
	models = make([]interface{}, len(tables))
	for i, tableName := range tables {
		models[i] = g.GenerateModel(tableName)
	}
	return models, nil
}

func main() {
	dsn := flag.String("dsn", "", "consult[https://gorm.io/docs/connecting_to_the_database.html]")
	dbType := flag.String("db", "mysql", "input mysql or postgres or sqlite or sqlserver. consult[https://gorm.io/docs/connecting_to_the_database.html]")
	outPath := flag.String("outPath", "./dao/query", "specify a directory for output")
	outFile := flag.String("outFile", "", "query code file name, default: gen.go")
	withUnitTest := flag.Bool("withUnitTest", false, "generate unit test for query code")
	modelPkgName := flag.String("modelPkgName", "model", "generated model code's package name")
	fieldNullable := flag.Bool("fieldNullable", false, "generate with pointer when field is nullable")
	fieldWithIndexTag := flag.Bool("fieldWithIndexTag", false, "generate field with gorm index tag")
	fieldWithTypeTag := flag.Bool("fieldWithTypeTag", false, "generate field with gorm column type tag")
	flag.Parse()

	db, err := connectDB(DBType(*dbType), *dsn)
	if err != nil {
		log.Fatalln("connect db server fail:", err)
	}

	g := gen.NewGenerator(gen.Config{
		OutPath:           *outPath,
		OutFile:           *outFile,
		ModelPkgPath:      *modelPkgName,
		WithUnitTest:      *withUnitTest,
		FieldNullable:     *fieldNullable,
		FieldWithIndexTag: *fieldWithIndexTag,
		FieldWithTypeTag:  *fieldWithTypeTag,
	})

	g.UseDB(db)

	models, err := getModels(g, db, []string{})
	if err != nil {
		log.Fatalln("get tables info fail:", err)
	}

	structs, err := check.CheckStructs(db, models...)
	if err != nil {
		db.Logger.Error(context.Background(), "check struct fail: %v", err)
		panic("check struct fail")
	}

	for _, t := range structs {
		var (
			tablename  = t.TableName
			filename   = t.TableName
			structname = t.StructName
		)
		if len(filename) >= 4 && filename[:4] == "sys_" {
			filename = filename[4:]
		}
		if len(structname) >= 3 && structname[:3] == "Sys" {
			structname = structname[3:]
		}

		if FileExist(filename + "_gen.go") {
			continue
		}
		f, err := os.Create(filename + "_gen.go")
		if err != nil {
			panic(err)
		}

		f.WriteString("package " + *modelPkgName + "\r\n")
		f.WriteString("\r\n")
		f.WriteString("type " + structname + " struct {\r\n")
		f.WriteString("\tBase\r\n")
		uuid_name := ""
		for _, m := range t.Members {
			if m.Name == "CreatedAt" || m.Name == "UpdatedAt" || m.Name == "DeletedAt" || m.Name == "ID" {
				continue
			}
			f.WriteString(fmt.Sprintf("\t%s %s `json:\"%s\"`", m.Name, m.Type, m.JSONTag))
			if m.ColumnComment != "" {
				f.WriteString(fmt.Sprintf(" // %s", m.ColumnComment))
				if len(m.ColumnComment) > 4 && m.ColumnComment[:4] == "uuid" {
					uuid_name = m.Name
				}
			}
			f.WriteString("\r\n")
		}
		f.WriteString("}\r\n")
		f.WriteString("\r\n")
		f.WriteString("func (m *" + structname + ") TableName() string {\r\n\treturn \"" + tablename + "\"\r\n}\r\n")
		if uuid_name != "" {
			f.WriteString("func (m *" + structname + ") AfterCreate(tx *gorm.DB) error {\r\n\tm." + uuid_name + " = idutilx.GetUuid(m.ID, \"\")\r\n\treturn tx.Table(m.TableName()).Save(m).Error\r\n}\r\n")
		}
		f.Close()

		genrepo(t, *modelPkgName)
	}
}

func genrepo(t *check.BaseStruct, modelPkgName string) error {
	var (
		//tablename  = t.TableName
		filename   = t.TableName
		structname = t.StructName
	)
	if len(filename) >= 4 && filename[:4] == "sys_" {
		filename = filename[4:]
	}
	if len(structname) >= 3 && structname[:3] == "Sys" {
		structname = structname[3:]
	}

	f, err := os.Create(filename + "_repo_gen.go")
	if err != nil {
		panic(err)
	}

	f.WriteString("package " + modelPkgName + "\r\n")
	f.WriteString("\r\n")

	f.WriteString("import \"ljzn-sass-cloud/pkg/resx\"\r\n")
	f.WriteString("\r\n")
	//f.WriteString("import (\r\n\t\"gorm.io/gorm\"\r\n)\r\n")
	//f.WriteString("\r\n")

	f.WriteString("type " + structname + "Repo struct {\n\tBaseRepo\n}")
	f.WriteString("\r\n")

	// FindOne
	f.WriteString("func (r *" + structname + "Repo) FindOne(option ...Option) (*" + structname + ", error) {\r\n")
	f.WriteString("\tvar m " + structname + "\r\n")
	f.WriteString("\tdb := r.DB.Model(&m)\r\n")
	f.WriteString("\tDoOption(db, option...)\r\n")
	f.WriteString("\terr := db.Take(&m).Error\r\n")
	f.WriteString("\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &m, nil\r\n}\r\n")
	f.WriteString("\r\n")

	// FindList
	f.WriteString("func (r *" + structname + "Repo) FindList(option ...Option) ([]*" + structname + ", error) {\r\n")
	f.WriteString("\tvar list []*" + structname + "\r\n")
	f.WriteString("\tdb := r.DB.Model(&list)\r\n")
	f.WriteString("\tDoOption(db, option...)\r\n")
	f.WriteString("\terr := db.Scan(&list).Error\r\n")
	f.WriteString("\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn list, nil\r\n}\r\n")
	f.WriteString("\r\n")

	// FindListByRes
	f.WriteString("func (r *" + structname + "Repo) FindListByRes(res interface{},option ...Option) error {\r\n")
	f.WriteString("\tdb := r.DB.Model(&" + structname + "{})\r\n")
	f.WriteString("\tDoOption(db, option...)\r\n")
	f.WriteString("\treturn db.Scan(res).Error\r\n")
	f.WriteString("}\r\n")
	f.WriteString("\r\n")

	// GetCount
	f.WriteString("func (r *" + structname + "Repo) GetCount(option ...Option) (int64, error) {\r\n")
	f.WriteString("\tvar count int64\r\n")
	f.WriteString("\tdb := r.DB.Model(&" + structname + "{})\r\n")
	f.WriteString("\tDoOption(db, option...)\r\n")
	f.WriteString("\terr := db.Count(&count).Error\r\n")
	f.WriteString("\tif err != nil {\n\t\treturn 0, err\n\t}\n\treturn count, nil\r\n}\r\n")
	f.WriteString("\r\n")

	// FindByPage
	f.WriteString("func (r *" + structname + "Repo) FindByPage(page resx.Page, option ...Option) ([]*" + structname + ", int64, error) {\r\n")
	f.WriteString("\tcount, err := r.GetCount(option...)\r\n")
	f.WriteString("\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n")
	f.WriteString("\tif count < 1 {\n\t\treturn nil, 0, nil\n\t}\n")
	f.WriteString("\toption = append(option, WithPage(page))\r\n")
	f.WriteString("\tlist, err := r.FindList(option...)\r\n")
	f.WriteString("\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n")
	f.WriteString("\treturn list, count, nil\r\n")
	f.WriteString("}\r\n")
	f.WriteString("\r\n")

	// FindByPageRes
	f.WriteString("func (r *" + structname + "Repo) FindByPageRes(page resx.Page, list interface{}, count *int64, option ...Option) error {\r\n")
	f.WriteString("\tc, err := r.GetCount(option...)\r\n")
	f.WriteString("\tif err != nil {\n\t\treturn err\n\t}\n")
	f.WriteString("\tif c < 1 {\n\t\treturn nil\n\t}\n")
	f.WriteString("\toption = append(option, WithPage(page))\r\n")
	f.WriteString("\terr = r.FindListByRes(list,option...)\r\n")
	f.WriteString("\tif err != nil {\n\t\treturn err\n\t}\n")
	f.WriteString("\t*count = c\n")
	f.WriteString("\treturn nil\r\n")
	f.WriteString("}\r\n")
	f.WriteString("\r\n")

	// Create
	f.WriteString("func (r *" + structname + "Repo) Create(m *" + structname + ") error {\r\n")
	f.WriteString("\treturn r.DB.Create(m).Error\r\n")
	f.WriteString("}\r\n")
	f.WriteString("\r\n")

	// Update
	f.WriteString("func (r *" + structname + "Repo) UpdateByMap(editMap map[string]interface{}, option ...Option) error {")
	f.WriteString("\tdb := r.DB.Model(&" + structname + "{})\r\n")
	f.WriteString("\tDoOption(db, option...)\r\n")
	f.WriteString("\treturn db.Updates(editMap).Error\r\n")
	f.WriteString("}\r\n")
	f.WriteString("\r\n")

	// Delete
	f.WriteString("func (r *" + structname + "Repo) Delete(option ...Option) error {")
	f.WriteString("\tdb := r.DB.Model(&" + structname + "{})\r\n")
	f.WriteString("\tDoOption(db, option...)\r\n")
	f.WriteString("\treturn db.Delete(&" + structname + "{}).Error\r\n")
	f.WriteString("}\r\n")
	f.WriteString("\r\n")

	f.Close()
	return nil
}

func FileExist(path string) bool {
	_, err := os.Lstat(path)
	return !os.IsNotExist(err)
}

//./genmodel -dsn "root:123456@tcp(127.0.0.1:3306)/farm_self?charset=utf8mb4&parseTime=True&loc=Local"
